#!/usr/bin/ruby
#
# Copyright:: Copyright 2012, Trimble Navigation Limited
# License:: All Rights Reserved.
# Original Author:: Scott Lininger (mailto:scott@sketchup.com)
#
# This file declares a DCFunctions class that contains all of the
# Open Office functions that can be called by the dynamic components
# formula parser. It is versioned as V1 so that we can later support 
# new versions as the library grows.
#
# These functions are designed to be compatible with those in Google 
# Spreadsheets. Extra documentation can be found here:
# 
# http://docs.google.com/support/spreadsheets/bin/answer.py?answer=82712
#
#     DCFunctionsV1          Contains open office wrapper methods

class DCFunctionsV1
  
  # TODO(Extensibility): Put this in a static method so it can be used by other
  # classes
  def is_supported(function_name)
    # Ruby 1.8 uses strings, Ruby 2.0 uses symbols.
    self.protected_methods.include?(function_name) ||
    self.protected_methods.include?(function_name.intern)
  end
  
  def call(function_name,param_array,source_entity=nil,dc=nil)
    @dc = dc
    @source_entity = source_entity
    return_val = self.send(function_name,param_array) 
    if return_val.kind_of? Float
      if return_val.to_s.include? 'e-'
        return_val = 0.0
      end
    end
    return return_val
  end

  #-- 
  #############################################################################
  #
  #  Support Functions
  #  (These are used below to simplify code, but are not called directly by
  #   the formula parser.)
  #
  #############################################################################
  #++

  # Returns two Hashes containing the total count of each entity type inside
  # a given entity. For example, a group containing a cube would return:
  # 
  # 
  #
  #   Args:
  #     entity: The group or component instance to do the counting for
  #
  #   Returns:
  #     ent_counts: a Hash with keys from subentity.typename.downcase
  #     material_areas: a Hash with keys from material.name.downcase, in square 
  #                     inches. Unpainted faces are totalled in a key of "none".
  #                     Also, material names are stripped of []<> to ease
  #                     matching of those names. (The brackets represent the 
  #                     fact that a material is of SU5 or SU6 origin, though
  #                     they are not consistently shown in the SU UI.)
  def subentity_counts(entity)
    
    # Define the Hashes that we will return.
    material_areas = {}
    ent_counts = {}

    if entity.typename == 'Group'
      entities = entity.entities
    else
      entities = entity.definition.entities
    end
    
    # Loop across subentities and total up the areas and entity counts
    for subentity in entities
      
      typename = subentity.typename.downcase
      if ent_counts[typename] == nil
        ent_counts[typename] = 0
      end
      ent_counts[typename] = ent_counts[typename] + 1
      
      if subentity.typename == "Face"

        if subentity.material == nil
          front_mat = "none"
        else
          front_mat = subentity.material.name
        end
        front_mat = front_mat.gsub(/[\<\>\[\]]/,'')
        front_mat = front_mat.downcase
        if material_areas[front_mat] == nil
          material_areas[front_mat] = 0.0
        end
        material_areas[front_mat] += subentity.area.to_f

        if subentity.back_material == nil
          back_mat = "none";
        else
          back_mat = subentity.back_material.name
        end
        back_mat = back_mat.gsub(/[\<\>\[\]]/,'')
        back_mat = back_mat.downcase
        if material_areas[back_mat] == nil
          material_areas[back_mat] = 0.0
        end
        material_areas[back_mat] += subentity.area.to_f

      end
    end
    return ent_counts,material_areas
    
  end

  # Takes two parameters. If the first parameter "has a value," meaning it's 
  # not null or false, then the method return the first parameter. Otherwise
  # it return the 2nd parameter.
  #
  #   Args:
  #     val1: first parameter to check
  #     val2: second parameter to return if the first is "empty"
  #
  #   Returns:
  #     object reference: either val1 or val2
  def second_if_empty(val1,val2)
    if val1 == nil
      return val2
    elsif val1 == false
      return val2
    else
      return val1
    end
  end

  protected

  #-- 
  #############################################################################
  #
  #  SketchUp Specific Functions
  #  (These will not be found in Google Spreadsheets)
  #
  #############################################################################
  #++

  def current(param_array)
    name = param_array[0].to_s.downcase
    if name == "lenx" || name == "leny" || name == "lenz" ||
       name == "x" || name == "y" || name == "z"
      return @dc.get_live_value(@source_entity, name)
    else
      value = @dc.get_attribute_value(@source_entity, name)
      if value == nil
        raise "attribute " + name + " not found"
      end
      return value
    end
  end

  def smallest(param_array)
    smallest = nil
    for number in param_array
      number = number.to_f
      if smallest == nil
        smallest = number
      elsif number < smallest
        smallest = number
      end
    end
    return smallest
  end

  def largest(param_array)
    largest = nil
    for number in param_array
      number = number.to_f
      if largest == nil
        largest = number
      elsif number > largest
        largest = number
      end
    end
    return largest
  end

  def choose(param_array)
    choice_index = param_array[0].to_i
    if choice_index < 1 || choice_index > param_array.length-1
      raise choice_index.to_s + " is out of range"
    else
      return param_array[choice_index]
    end
  end

  def nearest(param_array)
    search_value = param_array[0].to_f
    nearest_value = nil
    smallest_difference = nil
    for i in 1..param_array.length-1
      this_value = param_array[i].to_f
      this_difference = (this_value-search_value).abs
      if nearest_value == nil
        nearest_value = this_value
        smallest_difference = this_difference
      elsif this_difference < smallest_difference
        nearest_value = this_value
        smallest_difference = this_difference
      end
    end
    return nearest_value
  end

  # Returns the total number of faces inside the current group or component.
  #
  #   Args:
  #     param_array[0]: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: Total number of faces.
  def faces(param_array)
    entity_counts,material_areas = subentity_counts(@source_entity)
    return second_if_empty(entity_counts["face"],0.0).to_f
  end

  # Returns the total number of edges inside the current group or component.
  #
  #   Args:
  #     param_array[0]: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: Total number of edges.
  def edges(param_array)
    entity_counts,material_areas = subentity_counts(@source_entity)
    return second_if_empty(entity_counts["edge"],0.0).to_f
  end

  # Returns the total area of all faces matching the material name passed in,
  # that are inside the current component or group, in square inches. If no
  # name is passed in, it returns the total area of all faces.
  #
  #   Args:
  #     param_array[0]: Optional string name of the material to report on
  #
  #   Returns:
  #     float: Square inches that are painted with the given material
  def facearea(param_array)
    material_name = param_array[0].to_s
    material_name = material_name.gsub(/[\<\>\[\]]/,'')
    material_name = material_name.downcase
    entity_counts,material_areas = subentity_counts(@source_entity)
    if material_name == ''
      total = 0.0
      for area in material_areas.values
        total = total + area
      end
      return total
    else
      return second_if_empty(material_areas[material_name],0.0).to_f
    end
  end


  # Returns the Latitude of the current model.
  #
  #   Args:
  #     param_array: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: the Latitude of the current model
  def lat(param_array)
    shadow_info = Sketchup.active_model.shadow_info
    return second_if_empty(shadow_info["Latitude"].to_f,0.0)
  end

  # Returns the Longitude of the current model.
  #
  #   Args:
  #     param_array: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: the Longitude of the current model
  def lng(param_array)
    shadow_info = Sketchup.active_model.shadow_info
    return second_if_empty(shadow_info["Longitude"].to_f,0.0)
  end

  # Returns the angle, in degrees, between a north-facing vector of the current
  # model and the direction the sun is shining, as measured in a clockwise sweep
  # along the ground. For example, a spot close to the equator would return
  # ~90.0 for at sunrise, and ~270 at sunset.
  #
  #   Args:
  #     param_array: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: The angle, in degrees, between the sun and north
  def sunangle(param_array)
    shadow_info = Sketchup.active_model.shadow_info
    sun_vector = shadow_info["SunDirection"]
    sun_vector_on_ground = Geom::Vector3d.new(sun_vector[0],sun_vector[1],0)

    north_angle = shadow_info["NorthAngle"]
    north_radians = (north_angle/180.0)*Math::PI
    north_x = Math.sin(north_radians)
    north_y = Math.cos(north_radians)
    
    north_vector = Geom::Vector3d.new(north_x,north_y,0)
    east_vector = Geom::Vector3d.new(north_y,-north_x,0)

    # Commented code useful for testing or debugging
    # origin = Geom::Point3d.new 0,0,0 
    # Sketchup.active_model.entities.add_cline origin, sun_vector
    # Sketchup.active_model.entities.add_cline origin, sun_vector_on_ground
    # Sketchup.active_model.entities.add_cline origin, north_vector
    # Sketchup.active_model.entities.add_cline origin, east_point

    angle = north_vector.angle_between sun_vector_on_ground
    angle = angle * (180.0/Math::PI)

    east_angle = east_vector.angle_between sun_vector_on_ground
    east_angle = east_angle * (180.0/Math::PI)

    # If we've rotated over halfway around the circle from the north vector,
    # then this is the way that we know to show angles greater than 180. (By
    # default, the angle_between method will never show greater than 180.)
    if east_angle > 90.0
      angle = 360.0 - angle
    end
    return angle
  end

  # Returns the angle, in degrees, between the ground plane and the sun.
  # For example, a spot close to the equator would return ~90.0 at high noon.
  #
  #   Args:
  #     param_array: Though passed, it is not used in this function.
  #
  #   Returns:
  #     float: The angle, in degrees, between the sun and ground plane
  def sunelevation(param_array)
    shadow_info = Sketchup.active_model.shadow_info
    sun_vector = shadow_info["SunDirection"]
    sun_vector_on_ground = Geom::Vector3d.new(sun_vector[0],sun_vector[1],0)
    elevation = sun_vector.angle_between sun_vector_on_ground
    return elevation * (180/Math::PI)
  end

  # Returns the currently selected option index of the named attribute. Returns
  # -1 if none matches.
  #
  #   Args:
  #     param_array: String name of the attribute to get the index for
  #
  #   Returns:
  #     float: The index of the attribut's current selection
  def optionindex(param_array)
    name = param_array[0].to_s.downcase
    value = @dc.get_attribute_value(@source_entity,name)
    formula = @dc.get_attribute_formula(@source_entity,name)
    options = @dc.get_attribute_options(@source_entity,name)
    
    value = @dc.second_if_empty(value,'')
    formula = @dc.second_if_empty(formula,'')
    options = @dc.second_if_empty(options,'')

    options = options.gsub(/&quot;/,'"')
    
    labels = []
    values = []

    pairs_array = options.split(/&/)
    for pair in pairs_array
      if pair != ""
        label_value = pair.split(/=/)
        labels.push @dc.unescape(label_value[0])
        values.push @dc.unescape(label_value[1])
      end
    end

    index = values.index('=' + formula)
    if index == nil
      index = values.index(value)
    end
    if index == nil
      index = -1
    else
      index = index + 1
    end
    return index
  end

  # Returns the currently selected option label of the named attribute. Raises an
  # error if none matches.
  #
  #   Args:
  #     param_array[0]: String name of the attribute to get the label for
  #
  #   Returns:
  #     float: The label of the attribut's current selection
  def optionlabel(param_array)
    name = param_array[0].to_s.downcase
    value = @dc.get_attribute_value(@source_entity,name)
    formula = @dc.get_attribute_formula(@source_entity,name)
    options = @dc.get_attribute_options(@source_entity,name)
    
    value = @dc.second_if_empty(value,'')
    formula = @dc.second_if_empty(formula,'')
    options = @dc.second_if_empty(options,'')

    options = options.gsub(/&quot;/,'"')
    
    labels = []
    values = []

    pairs_array = options.split(/&/)
    for pair in pairs_array
      if pair != ""
        label_value = pair.split(/=/)
        labels.push @dc.unescape(label_value[0])
        values.push @dc.unescape(label_value[1])
      end
    end

    index = values.index('=' + formula)
    if index == nil
      index = values.index(value)
    end
    if index == nil
      raise @dc.translate("No match found.")
    else
      return labels[index]
    end
  end

  #-- 
  #############################################################################
  #
  #  onClick Functions - These only work in context of an onClick, but return 
  #                      a useful error if called inside a regular formula
  #
  #############################################################################
  #++
  def set(param_array)
    raise @dc.translate("SET cannot be called in a formula")
  end

  def animate(param_array)
    raise @dc.translate("ANIMATE cannot be called in a formula")
  end

  def animateslow(param_array)
    raise @dc.translate("ANIMATESLOW cannot be called in a formula")
  end

  def animatefast(param_array)
    raise @dc.translate("ANIMATEFAST cannot be called in a formula")
  end

  def alert(param_array)
    raise @dc.translate("ALERT cannot be called in a formula")
  end

  def animatecustom(param_array)
    raise @dc.translate("ANIMATECUSTOM cannot be called in a formula")
  end

  def gotoscene(param_array)
    raise @dc.translate("GOTOSCENE cannot be called in a formula")
  end

  def redraw(param_array)
    raise @dc.translate("REDRAW cannot be called in a formula")
  end

  #-- 
  #############################################################################
  #
  #  Math Functions
  #
  #############################################################################
  #++

  def abs(param_array)
    number = param_array[0]
    return number.to_f.abs
  end

  def acos(param_array)
    number = param_array[0]
    return degrees(Math.acos(number.to_f))
  end

  def acosh(param_array)
    number = param_array[0]
    return degrees(Math.acosh(number.to_f))
  end

  def asin(param_array)
    number = param_array[0]
    return degrees(Math.asin(number.to_f))
  end

  def asinh(param_array)
    number = param_array[0]
    return degrees(Math.asinh(number.to_f))
  end

  def atan(param_array)
    number = param_array[0]
    return degrees(Math.atan(number.to_f))
  end

  # atan2

  def atanh(param_array)
    number = param_array[0]
    return degrees(Math.atanh(number.to_f))
  end

  def ceiling(param_array)
    number = param_array[0]
    significance = param_array[1]
    if significance == nil || significance.to_f == 0.0
      significance = 1.0
    else
      significance = significance.to_f
    end
    number = number.to_f / significance
    # Google spreadsheet and ruby vary in how they do ceilings on negative
    # numbers, so we must do a manual fix.
    if number > 0.0
      number = number.ceil
    else
      number = number.floor
    end 
    number = number * significance
    if significance >= 1
      number = number.to_i
    end
    return number
  end

  # combin

  def cos(param_array)
    return Math.cos(radians(param_array))
  end

  def cosh(param_array)
    return Math.cosh(radians(param_array))
  end

  def degrees(param_array)
    # degrees is called in several of our math functions, so for simplicity
    # this is method can take a float directly
    if param_array.kind_of? Float
      number = param_array
    else
      number = param_array[0]
    end
    number = number.to_f*(180.0/Math::PI)
    return number
  end

  def even(param_array)
    number = param_array[0]
    return ((number.to_f/2).ceil)*2
  end

  def exp(param_array)
    number = param_array[0]
    return Math.exp(number.to_f)
  end

  # fact

  # factdouble

  def floor(param_array)
    number = param_array[0]
    significance = param_array[1]
    if significance == nil || significance.to_f == 0.0
      significance = 1.0
    else
      significance = significance.to_f
    end
    number = number.to_f / significance
    number = number.floor
    number = number * significance
    if significance >= 1
      number = number.to_i
    end
    return number
  end

  # gcd

  def int(param_array)
    number = param_array[0]
    return number.to_f.floor
  end

  def iseven(param_array)
    number = param_array[0].to_f
    # Account for differences between ruby's floor and Google Spreadsheet's
    # floor.
    if number < 0.0
      number = number.ceil
    else
      number = number.floor
    end
    param_array[0] = number
    if number!=even(param_array)
      return 0.0
    else
      return 1.0
    end
  end

  def isodd(param_array)
    is_even = iseven(param_array)
    if is_even == 1.0
      return 0.0
    else
      return 1.0
    end
  end

  # lcm

  def ln(param_array)
    number = param_array[0]
    return Math.log(number.to_f)
  end

  #log

  def log10(param_array)
    number = param_array[0]
    return Math.log10(number.to_f)
  end

  def odd(param_array)
    number = param_array[0]
    return (((number.to_f-1.0)/2).ceil)*2+1.0
  end

  def pi(param_array)
    return Math::PI
  end

  def power(param_array)
    base = param_array[0].to_f
    power = param_array[1].to_f
    return (base**power)
  end

  #product(param_array 1 to 30)

  #*quotient(numerator, denominator)

  def radians(param_array)
    number = param_array[0]
    return (number.to_f * (Math::PI/180))
  end

  def rand(param_array)
    Kernel::rand()
  end

  def randbetween(param_array)
    bottom = param_array[0].to_f
    top = param_array[1].to_f
    range = top-bottom + 1.0
    result = ((range)*Kernel::rand()+bottom).floor
  end

  def round(param_array)
    number = param_array[0].to_f
    digits = param_array[1].to_f
    count = 0.0
    significance = 1.0
    while count < digits
      significance = significance/10
      count = count + 1
    end
    number = number.to_f / significance
    number = number.round
    number = number * significance
    if significance >= 1
      number = number.to_i
    end
    return number
  end

  #rounddown(param_array, count)

  #roundup(param_array, count)

  #*seriessum(x, n, m, coefficients)

  def sign(param_array)
    number = param_array[0]
    number = number.to_f
    if number < 0.0
      return -1.0
    elsif number > 0.0
      return 1.0
    else
      return 0.0
    end
  end

  def sin(param_array)
    return Math.sin(radians(param_array))
  end

  def sinh(param_array)
    return Math.sinh(radians(param_array))
  end

  def sqrt(param_array)
    number = param_array[0]
    return Math.sqrt(number.to_f)
  end

  #*sqrtpi(param_array)

  #sum(param_array_1, number_2, ... number_30)

  #sumif(range, criteria, sum_range)

  #sumsq(param_array_1, number_2, ... number_30)

  def tan(param_array)
    return Math.tan(radians(param_array))
  end

  def tanh(param_array)
    return Math.tanh(radians(param_array))
  end

  #trunc(param_array, count)
  
  #-- 
  #############################################################################
  #
  #  Logical Functions
  #
  #############################################################################
  #++

  def and(param_array)
    all_true = true
    for value in param_array
      if value.to_f == 0.0
        all_true = false
      end
    end
    if all_true
      return 1.0
    else
      return 0.0
    end
  end  

  def false(param_array)
    return 0.0
  end  

  def if(param_array)
    if_value = param_array[0].to_f
    if if_value <= 0.0
      return param_array[2]
    else
      return param_array[1]
    end
  end  

  #def iferror(test, value)
  #end  

  def not(param_array)
    value = param_array[0]
    if value.to_f == 0.0
      return 1.0
    else
      return 0.0
    end
  end  

  def or(param_array)
    any_true = false
    for value in param_array
      if value.to_f != 0.0
        any_true = true
      end
    end
    if any_true
      return 1.0
    else
      return 0.0
    end
  end

  def true(param_array)
    return 1.0
  end

  #-- 
  #############################################################################
  #
  #  Text Functions
  #
  #############################################################################
  #++

  def char(param_array)
    number = param_array[0].to_i
    if number == 32
      return ' '
    else
      # This works on both Ruby 1.8 and Ruby 2.0.
      char = [number].pack('U*')
      # Note that the 2nd "false" parameter in the escape function
      # instructs the method to escape spaces with %20 instead of +,
      # (the + character gets the parser confused)
      return @dc.escape(char,false)
    end
  end

  def code(param_array)
    text = param_array[0].to_s
    text = @dc.unescape(text)
    # This works on both Ruby 1.8 and Ruby 2.0.
    return text.unpack('U*').first
  end

  def concatenate(param_array)
    value = ""
    for str in param_array 
      value = value + str.to_s
    end
    return value
  end

  def dollar(param_array)
    val = param_array[0].to_f
    decimals = param_array[1] 
   
    whole = val.floor.to_i

    # compute fraction, 0-padding if < 10
    fraction = (((val-whole.to_f)*100).round).to_i
    if (fraction == 100)
      fraction = 0
      whole = whole + 1
    end 
    if fraction < 10
      fraction = "0" + fraction.to_s
    else
      fraction = fraction.to_s
    end

    whole = whole.to_s
    formatted_whole = '';
    count = 0;
    for i in 0..(whole.length-1)
      if (count == 3)
        formatted_whole = "," + formatted_whole
        count = 0
      end
      count = count + 1
      formatted_whole = whole[whole.length-1-i..whole.length-1-i] + formatted_whole;
    end

    return "$" + formatted_whole.to_s + "." + fraction

  end

  def exact(param_array)
    text_1 = param_array[0].to_s
    text_2 = param_array[1].to_s
    if text_1 == text_2
      return true
    else
      return false
    end
  end

  def find(param_array)
    find_text = param_array[0].to_s
    find_text = @dc.unescape(find_text)
    text = param_array[1].to_s
    text = @dc.unescape(text)
    offset = param_array[2]
    if offset == nil
      offset = 0
    else
      offset = offset.to_i-1
    end
    if offset < 0
      raise ("Offset must be greater than 0")
    end
    pos = text.index(find_text,offset)
    if pos == nil
      raise("Not found: " + find_text)
    else 
      # The first character in open office is at position 1, whereas Ruby's
      # String::index is 0 based, so add one to our result.
      return pos+1
    end
  end

  # This function is deferred, as it's not terribly useful and it's hard to 
  # implement.
  #
  # def fixed(param_array)
  #   number = param_array[0]
  #   decimals = param_array[1]
  #   no_thousands_separator = param_array[0] 
  # end

  def left(param_array)
    text = param_array[0].to_s
    text = @dc.unescape(text)
    number = param_array[1]
    if number == nil
      number = 0
    else
      number = param_array[1].to_i - 1
    end
    if number == -1
      return ""
    end
    if number >= 0
      # Note that the 2nd "false" parameter in the escape function
      # instructs the method to escape spaces with %20 instead of +,
      # (the + character gets the parser confused)
      return @dc.escape(text[0..number],false)
    else
      raise "Argument negative"
    end
  end

  def len(param_array)
    string = param_array[0].to_s
    string = @dc.unescape(string)
    return string.length
  end

  def lower(param_array)
    text = param_array[0].to_s
    return text.downcase
  end

  def mid(param_array)
    text = param_array[0].to_s
    text = @dc.unescape(text)
    start_pos = param_array[1].to_i - 1
    length = param_array[2].to_i - 1
    if start_pos < 0
      raise "Argument out of range:" + param_array[1].to_i.to_s
    end
    if length < 0
      raise "Argument out of range:" + param_array[2].to_i.to_s
    end
    # Note that the 2nd "false" parameter in the escape function
    # instructs the method to escape spaces with %20 instead of +,
    # (the + character gets the parser confused)
    return @dc.escape(text[start_pos..start_pos+length],false)
  end

  def proper(param_array)
    text = param_array[0].to_s
    words = text.split(/%20/)
    for i in 0..words.length-1
      words[i] = words[i].to_s.downcase.capitalize
    end
    return_value = words.join(' ')
    return return_value
  end


  def replace(param_array)
    text = param_array[0].to_s
    position = param_array[1].to_i - 1
    length = param_array[2].to_i - 1
    new_text = param_array[3].to_s
    
    start_str = text[0..position-1]
    end_str = text[position+length+1..-1]
    return start_str + new_text + end_str
  end

  def rept(param_array)
    text = param_array[0].to_s
    number = param_array[1].to_i
    str = ""
    for i in 1..number
      str = str + text
    end
    return str
  end

  def right(param_array)
    text = param_array[0].to_s
    text = @dc.unescape(text)
    number = param_array[1]
    if number == nil
      number = 0
    else
      number = param_array[1].to_i - 1
    end
    if number == -1
      return ""
    end
    if number >= 0
      start = text.length-1-number
      if start < 0
        start = 0
      end
      # Note that the 2nd "false" parameter in the escape function
      # instructs the method to escape spaces with %20 instead of +,
      # (the + character gets the parser confused)
      return @dc.escape(text[start..text.length-1],false)
    else
      raise "Argument negative"
    end
  end

  def search(param_array)
    return find(param_array)
  end

  def substitute(param_array)
    text = param_array[0].to_s
    search_text = param_array[1].to_s
    new_text = param_array[2].to_s
    occurrence = param_array[3].to_i
    reg_exp = Regexp.new(search_text, true)
    for i in 1..occurrence
      text = text.sub(reg_exp,new_text)
    end
    return text
  end

  # This function is deferred, as it's not useful, AFAIK.
  #
  # def t(param_array)
  #   value = param_array[0]
  #  return value
  # end

  # This function is deferred, as it's not terribly useful and it's hard to 
  # implement.
  #
  # def text(param_array)
  #  number = param_array[0]
  #  format = param_array[1]
  # end

  def trim(param_array)
    text = param_array[0].to_s
    text = text.gsub(/\%20/,' ')
    return text.strip
  end

  def upper(param_array)
    text = param_array[0].to_s
    return text.upcase
  end

  def value(param_array)
    text = param_array[0].to_s
    text = text.gsub(/\%2D/,'-')
    return text.to_f
  end



end